/*
 * Note from shayner: Most of this is an example taken from the internet at
 * http://gpwiki.org/index.php/C:Playing_a_WAV_Sound_File_With_SDL_mixer
 * and also at
 * http://gpwiki.org/index.php/C:Using_SDL_mixer_to_play_an_Ogg_music_file
 * 
 * However the parts of musicDoCoolStuff and rendering the music is all me,
 * which is why it isn't so formal with the determination of channels and
 * endianess, or even properly locking the musicPlaying for multithreaded
 * goodness.
 * 
 * Use it and learn, boy! 
 */
#include <string>
#include <iostream>
 
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_gfxPrimitives.h>
#include <cmath>

using namespace std;

const char* MUSIC_FILE = "/home/shayne/Desktop/audio.ogg";
const Uint32 MILLISECONDS_PER_FRAME = 21;
const int CHANNELS = 2;
const int AUDIOBUFFER = 4096;
const int START_X_RES = 940;
const int START_Y_RES = 705;
const int AUDIO_RATE = 44100; // In Hertz.
const float SECONDS_PER_SAMPLE = 1/((float)AUDIO_RATE);
//const float MAX_DIFF = 0xAC4353BC; // Maximum differential allowed.
const float MAX_DIFF = 0x5621A9DE; // Maximum differential allowed.
const float MAX_DIFF_SQRT = sqrt(MAX_DIFF); // result of square root on the max.

Sint16 channel[CHANNELS][AUDIOBUFFER];
int channelSamples = AUDIOBUFFER;

int musicPlaying = 0;

SDL_Surface *screen;

void musicFinished()
{
	musicPlaying = 0;
}

void musicDoCoolStuff(void *udata, Uint8 *stream, int len)
{
	// Assumptions:
	// Audio is two channels
	// Each audio sample is signed 16-bit little endian.
	//
	// If two channels, then length should be split into half.
	// If 16-bit little endian, then every 2n byte is the least significate byte
	// and every 2n+1 byte is most significant. Therefore, if we encounter
	// a buffer with a size of 8 bytes: [0] [1] [2] [3] [4] [5] [6] [7]
	// then we will actually have 4 signed 16-bit values organized like this:
	// [2n-1][2n], so it follows the values are: [1][0] [3][2] [5][4] [7][6]
	// Neat huh? Well, that's x86 architechure for you.
	// But wait, there's more!
	// The channels data is interleaved. Every 2n value goes to the left
	// channel. Every 2n+1 value goes to the right.
	// In summary: first two bytes - left. next two bytes - right.

	for(int i = 0; i < len; i+=(2*CHANNELS))
	{
		for(int channelIndex = 0; channelIndex < CHANNELS; ++channelIndex)
		{
			channel[channelIndex][i/(2*CHANNELS)] = stream[i + (2*channelIndex)];	// Do not move the first byte...
			channel[channelIndex][i/(2*CHANNELS)] += (stream[i + (2*channelIndex) + 1] << 8);	// ...but shift the second.
		}
	}
	channelSamples = (len / CHANNELS / 2);
}

// http://en.wikipedia.org/wiki/HSL_and_HSV#Formal_specifications
void rgbToHSV(int inRed, int inGreen, int inBlue, float *outHue, float *outSaturation, float *outValue)
{
	// Identify the max and min
	int max = inRed;
	if(max < inGreen)
	{
		max = inGreen;
	}
	if(max < inBlue)
	{
		max = inBlue;
	}

	int min = inRed;
	if(inGreen < min)
	{
		min = inGreen;
	}
	if(inBlue < min)
	{
		min = inBlue;
	}
	
	int range = max - min;

	// Part 1: Identify Hue
	float angle = 0; // (0 to 360, divided down later)
	// case 1: max == min: hue is red (a.k.a. 0 degrees)
	if(range == 0)
	{
		angle = 0;
	}
	// case 2: max == r
	else if(max == inRed)
	{
		angle = ((float)60) * ((float)(inGreen-inBlue))/(float)range;
		// case 2.1: max == r and g < b
		if(inGreen < inBlue)
		{
			angle += (float)360; // Puts the angle back into the range of 0 to 360. 
		}
	}
	// case 3: max = g
	else if(max == inGreen)
	{
		angle = ((float)60) * ((float)(inBlue-inRed))/(float)range + (float)120;
	}
	// case 4: max = b
	else if(max == inBlue)
	{
		angle = ((float)60) * ((float)(inRed-inGreen))/(float)range + (float)240;
	}

	float hue = angle / (float)360;

	// Part 2: Identify Saturation
	float saturation = 0;
	if(0 < max)
	{
		saturation = ((float)range) / (float)max;
	}

	// Part 3: Indentify Value
	float value = (((float)max) / (float)255);

	// Put into the proper arguments.
	*outHue = hue;
	*outSaturation = saturation;
	*outValue = value;
}

// http://en.wikipedia.org/wiki/HSL_and_HSV#Formal_specifications
void hsvToRGB(float inHue, float inSaturation, float inValue, int *outRed, int *outGreen, int *outBlue)
{
	int hueIndex = ((int)(((float)inHue) * (float)6)) % 6;

	if(hueIndex < 0)
	{
		hueIndex += 6;
	}

	// wikipedia does not mention what f, p, q, or t means, unfortunately.
	float f = (inHue * ((float)6)) - (((float)hueIndex));
	int p = (int)((inValue * (((float)1) - inSaturation)) * 255);
	int t = (int)((inValue * (((float)1) - ((((float)1) - f) * inSaturation))) * 255);
	int q = (int)((inValue * (((float)1) - (f * inSaturation))) * 255);
	int iValue = (int)(inValue * 255);

	switch(hueIndex)
	{
	case 0:
		*outRed = iValue;
		*outGreen = t;		
		*outBlue = p;
		break;
	case 1:
		*outRed = q;
		*outGreen = iValue;
		*outBlue = p;
		break;
	case 2:
		*outRed = p;
		*outGreen = iValue;
		*outBlue = t;
		break;
	case 3:
		*outRed = p;
		*outGreen = q;
		*outBlue = iValue;
		break;
	case 4:
		*outRed = t;
		*outGreen = p;
		*outBlue = iValue;
		break;
	case 5:
		*outRed = iValue;
		*outGreen = p;
		*outBlue = q;
		break;
	default:
		cout << "Weird HSV value, returning black." << endl;
		cout << "H: " << inHue << " S: " << inSaturation << " V: " << inValue << endl;
		cout << "hueIndex should be between 0-5, instead got " << hueIndex << endl;
		*outRed = 0;
		*outGreen = 0;
		*outBlue = 0;
	}
}

/*
* backwardDiff - calculates the backward difference at currentSample.
* @param	leftSample	value of the sample to the left.
* @param	currentSample	value of the sample to care about.
* @return	Difference quotient.
*/
float backwardDiff( Sint16 leftSample, Sint16 currentSample )
{
//	// f'(x) = ( 3f(x) - 4f(x-h) + f(x-2h) )/2h + O(h^2)
//	return ( 3*currentSample - 4*leftSample + lefterSample )/(2*SECONDS_PER_SAMPLE);
	// f'(x) = f(x-h)-f(x)/h
	return (leftSample-currentSample)/(float)SECONDS_PER_SAMPLE;
}

void colorizeWave(Sint16 inLeftSample, Sint16 inCurrentSample, int *outRed, int *outGreen, int *outBlue, float *outScalePosition)
{
	float diff = backwardDiff(inLeftSample, inCurrentSample);

	// Ensure the values are in the appropriate limits.
	if(diff < MAX_DIFF * -1)
	{
		diff = MAX_DIFF * -1;
	}
	else if(MAX_DIFF < diff)
	{
		diff = MAX_DIFF;
	}

	// Square root it.
	float sign = 1;
	if(diff < 0)
	{
		sign = -1;
	}
	diff = sqrt(abs(diff)) * sign;

	// This makes things easier: 0 is low, 0.5 is mid, and 1 is high.
	float scalePosition = (diff + (MAX_DIFF_SQRT)) / (MAX_DIFF_SQRT * 2);
	*outScalePosition = scalePosition;

//	// Make a grayscale portion.
//	// Subtract from 1 to make white appear for high values, black for low,
//	// and gray for middlin'.
//	int colorValue = (int)((float)1 - scalePosition * (float)255);
//	*outRed = colorValue;
//	*outGreen = colorValue;
//	*outBlue = colorValue;

	// Make a colorized portion.
	// We must make the maximum Diff (1) be red (0 degrees), the minimum (0) be 
	// green (120 degrees), and normal (0.5) be yellow (60 degrees).
	// Multiply by 60/360 to get the low color to be green.
	float hue = ((float)1 - scalePosition) * (float)0.33333333;
	// now make into the proper color.
	hsvToRGB(hue, (float)1, (float)1, outRed, outGreen, outBlue);
//	hsvToRGB(hue, (float)1, abs((float)0.5 - scalePosition), outRed, outGreen, outBlue);
}

int main(int argc, char **argv)
{
	
	// Initialize SDL normally, but now with the audio enabled so that
	// SDL_mixer has access to the hardware.
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0)
	{
		cerr << "Unable to initialize SDL: " << SDL_GetError() << endl;
		return EXIT_FAILURE;
	}

	screen = SDL_GetVideoSurface();

	int audio_rate = AUDIO_RATE; // in Hertz.
	Uint16 audio_format = AUDIO_S16SYS; // There are other forms too.
	int audio_channels = CHANNELS; // Number of channels to use.
	int audio_buffers = AUDIOBUFFER; // Size of the buffers in bytes.

	// Initialize the mixer.
	if(Mix_OpenAudio(
			audio_rate, audio_format, audio_channels, audio_buffers) != 0)
	{
		cerr << "Unable to initialize audio: " << Mix_GetError() << endl;
		return EXIT_FAILURE;
	}

//	// Initialize the sound that is played at the very beginning, on top of the
//	// music.
//	Mix_Chunk *sound = NULL;
//	
//	sound = Mix_LoadWAV("/home/shayne/Desktop/sample.wav");
//	if(sound == NULL)
//	{
//		cerr << "Unable to load WAV file: " << Mix_GetError() << endl;
//		return EXIT_FAILURE;
//	}

	
	// Initialize the music to play in the background.
	Mix_Music *music;
	music = Mix_LoadMUS(MUSIC_FILE);
	if(music == NULL)
	{
		cout << "Unable to load Ogg file: " << Mix_GetError() << endl;
		return EXIT_FAILURE;
	}

	// Initialize the SDL window. Of course, this must occur or nothing works.
	SDL_Surface *screen;

	screen = SDL_SetVideoMode(START_X_RES, START_Y_RES, 0, SDL_DOUBLEBUF | SDL_ANYFORMAT | SDL_RESIZABLE);
	if (screen == NULL) {
		cerr << "Unable to set video mode: " << SDL_GetError() << endl;
		return EXIT_FAILURE;
	}

	// Start the music.
	if(Mix_PlayMusic(music, 0) == -1)
	{
		cerr << "Unable to play Ogg file: " << Mix_GetError() << endl;
		return EXIT_FAILURE;
	}

//	// Start the audio sample.
//	int channel;
//	
//	channel = Mix_PlayChannel(-1, sound, 0);
//	if(channel == -1)
//	{
//		cerr << "Unable to play WAV file: " << Mix_GetError() << endl;
//	}

	// Waif for music to stop
	musicPlaying = 1;
	Mix_HookMusicFinished(musicFinished);
	Mix_SetPostMix(musicDoCoolStuff, NULL);
	
	SDL_Event event;
	bool wantsToQuit = false;
	bool shouldPause = false;
	Uint32 frames = 0;
	Uint32 startTicks = SDL_GetTicks();
	while(musicPlaying && !wantsToQuit)
	{
		while(SDL_PollEvent(&event))
		{
			if(event.type == SDL_KEYUP)
			{
				if(event.key.keysym.sym == SDLK_q)
				{
					wantsToQuit = true;
				}
				else if(event.key.keysym.sym == SDLK_ESCAPE)
				{
					wantsToQuit = true;
				}
				else if(event.key.keysym.sym == SDLK_SPACE)
				{
					shouldPause = !shouldPause;
					if(shouldPause)
					{
						Mix_PauseMusic();
						// Reset framerate limiter. Way to go, Yeesh!
						Uint32 endTicks = SDL_GetTicks();
						cout << "Frames: " << frames << " Ticks: " << endTicks - startTicks << endl;
						frames = 0;
						startTicks = endTicks;
					}
					else
					{
						Mix_ResumeMusic();
					}
				}
			}
			else if(event.type == SDL_QUIT)
			{
				wantsToQuit = true;
			}
			else if(event.type == SDL_VIDEORESIZE)
			{
				Uint16 newWidth = event.resize.w;
				Uint16 newHeight = event.resize.h;
				screen = SDL_SetVideoMode(newWidth, newHeight, 0, SDL_DOUBLEBUF | SDL_ANYFORMAT | SDL_RESIZABLE);
				if (screen == NULL) {
					cerr << "Unable to resize video: " << SDL_GetError() << endl;
					return EXIT_FAILURE;
				}
			}
		}

		if(shouldPause)
		{
			SDL_Delay(63);
			startTicks = SDL_GetTicks(); // For when the player is unpaused.
			continue;
		}

		Sint16 xRes = screen->w;
		Sint16 yRes = screen->h;
		boxRGBA(screen, 0, 0, xRes-1, yRes-1, 0, 0, 0, 255);
		
		double ampHeight = ((double)yRes) / 4.0;
		
		Sint16 topMid = (Sint16)(yRes * .25);
		Sint16 midMid = (Sint16)(yRes * .5);
		Sint16 lowMid = (Sint16)(yRes * .75);

		Sint16 oldLeftAmp = topMid - (int)(ampHeight * ((double)channel[0][0]/32768.0));
		Sint16 oldRightAmp = lowMid - (int)(ampHeight * ((double)channel[1][0]/32768.0));
		Sint16 oldDifferenceAmp = midMid - (int)(ampHeight * ((double)(channel[0][0]-channel[1][0])/32768.0));

		hlineRGBA(screen, 0, xRes, topMid + (int)(ampHeight * 0.5), 127,127,0,255);
		hlineRGBA(screen, 0, xRes, topMid - (int)(ampHeight * 0.5), 127,127,0,255);
		for(int i = 1; i < channelSamples && i < xRes; ++i)
		{
			if(1 < CHANNELS) // Only draw difference if more than 1 channel
			{
				// Draw difference amplitude
				Sint16 differenceAmp = midMid - (int)(ampHeight * ((double)(channel[0][i]-channel[1][i])/32768.0));
				aalineRGBA(screen, i-1, oldDifferenceAmp, i, differenceAmp, 0xA0, 0xA0, 0, 0xFF);
				oldDifferenceAmp = differenceAmp;
			}
			// Draw left channel amplitude
			Sint16 leftAmp = topMid - (int)(ampHeight * ((double)channel[0][i]/32768.0));
			int red = 0;
			int green = 0;
			int blue = 0;
			float scalePos = (float)0;
			colorizeWave(channel[0][i-1], channel[0][i], &red, &green, &blue, &scalePos);
//			aalineRGBA(screen, i-1, oldLeftAmp, i, leftAmp, red, green, blue, 0xFF);
			vlineRGBA(screen, i, topMid-(int)(ampHeight*scalePos), topMid+(int)(ampHeight*scalePos), red, green, blue, 0xFF);
			oldLeftAmp = leftAmp;

			if(1 < CHANNELS)
			{
				// Draw right channel amplitude
				Sint16 rightAmp = lowMid - (int)(ampHeight * ((double)channel[1][i]/32768.0));
				aalineRGBA(screen, i-1, oldRightAmp, i, rightAmp, 0x80, 0xFF, 0x80, 0xFF);
				oldRightAmp = rightAmp;
			}
		}
		
		SDL_Flip(screen);

		// Do nothing for a bit
		frames++;
		Uint32 expectedTicks = MILLISECONDS_PER_FRAME * frames;
		Uint32 currentTicks = SDL_GetTicks()- startTicks;
		if(currentTicks < expectedTicks)
		{
			Uint32 delay = expectedTicks - currentTicks;
			if (100 < delay)
			{
				delay = 100;
			}
			SDL_Delay(delay);
		}
	}
	
	Uint32 endTicks = SDL_GetTicks();
	cout << "Frames: " << frames << " Ticks: " << endTicks - startTicks << endl;
//	Mix_FreeChunk(sound); // Release the sample from memory.

	Mix_HaltMusic(); // Stop playing the music, just in case.
	Mix_FreeMusic(music); // Release the music from memory.
	Mix_CloseAudio(); // Close the mixer.
	SDL_Quit(); // Shut down SDL nicely.

	cout << "Success!" << endl;

	return EXIT_SUCCESS;
}
